﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Interactivity.InteractionRequest;
using Microsoft.Practices.Prism.PubSubEvents;
using Mitosoft.Definitions.Events;
using Mitosoft.Definitions.Notifications;
using Mitosoft.Definitions.Services;

namespace Mitosoft.Definitions.ViewModels
{
  public class FilesSelectionDialogViewModel : BaseViewModel, IInteractionRequestAware
  {
    #region fields

    /// <summary>
    /// Represents <see cref="Notifications.FilesSelectionDialogNotification"/>
    /// </summary>
    protected FilesSelectionDialogNotification _filesSelectionDialogNotification;

    /// <summary>
    /// Represents <see cref="FileNames"/>
    /// </summary>
    protected SortedDictionary<int, IOViewModel> _fileNames;

    /// <summary>
    /// Represents <see cref="UnselectedDirectoryFileNames"/>
    /// </summary>
    protected ObservableCollection<IOViewModel> _unselectedDirectoryFileNames;

    /// <summary>
    /// Represents <see cref="SelectedDirectoryFile"/>
    /// </summary>
    protected IOViewModel _selectedDirectoryFile;

    /// <summary>
    /// Represents <see cref="SelectedFile"/>
    /// </summary>
    protected IOViewModel _selectedFile;

    /// <summary>
    /// Represents <see cref="DirectorySelectionViewModel"/>
    /// </summary>
    protected DirectorySelectionViewModel _directorySelectionViewModel;

    /// <summary>
    /// Represents <see cref="SelectFileCommand"/>
    /// </summary>
    protected ICommand _selectFileCommand;

    /// <summary>
    /// Represents <see cref="UnselectFileCommand"/>
    /// </summary>
    protected ICommand _unselectFileCommand;

    /// <summary>
    /// Represents <see cref="MoveUpCommand"/>
    /// </summary>
    protected ICommand _moveUpCommand;

    /// <summary>
    /// Represents <see cref="MoveDownCommand"/>
    /// </summary>
    protected ICommand _moveDownCommand;

    /// <summary>
    /// Represents <see cref="ZoomViewModel"/>
    /// </summary>
    protected ZoomViewModel _zoomViewModel;

    #endregion

    #region constructors

    /// <summary>
    /// Creates a new <see cref="FilesSelectionDialogViewModel"/> instance.
    /// </summary>
    public FilesSelectionDialogViewModel()
    {
      CancelDialogCommand = new DelegateCommand(CancelDialogCommandInteraction);
      AcceptDialogCommand = new DelegateCommand(AcceptDialogCommandInteraction);
    }

    #endregion

    #region properties

    /// <summary>
    /// Represents the view model containing all needed for zooming.
    /// </summary>
    public ZoomViewModel ZoomViewModel
    {
      get
      {
        if (_zoomViewModel == null)
        {
          _zoomViewModel = new ZoomViewModel();
          _zoomViewModel.PropertyChanged += ZoomViewModelPropertyChanged;
        }

        return _zoomViewModel;
      }
    }

    /// <summary>
    /// Gets whether <see cref="SelectFileCommand"/> is enabled.
    /// </summary>
    protected bool SelectFileCommandIsEnabled
    {
      get
      {
        return SelectedDirectoryFile != null && !FileNames.ContainsValue(SelectedDirectoryFile);
      }
    }

    /// <summary>
    /// Gets whether <see cref="UnselectFileCommand"/> is enabled.
    /// </summary>
    protected bool UnselectFileCommandIsEnabled
    {
      get
      {
        return SelectedFile != null && FileNames.ContainsValue(SelectedFile);
      }
    }

    /// <summary>
    /// Gets whether <see cref="MoveUpCommand"/> is enabled.
    /// </summary>
    protected bool MoveUpCommandIsEnabled
    {
      get
      {
        return OrderOfSelectedFile > 0;
      }
    }

    /// <summary>
    /// Gets whether <see cref="MoveDownCommand"/> is enabled.
    /// </summary>
    protected bool MoveDownCommandIsEnabled
    {
      get
      {
        return FileNames.Count > 1 && OrderOfSelectedFile != FileNames.Keys.Max();
      }
    }

    /// <summary>
    /// Gets the order of the selected file.
    /// </summary>
    protected int OrderOfSelectedFile
    {
      get
      {
        if (SelectedFile == null) return -1;
        if (!FileNames.Any()) return -1;

        try
        {
          return FileNames.First(orderAndFileKVP => orderAndFileKVP.Value == SelectedFile).Key;
        }
        catch
        {
          return -1;  // Shouldn`t happen.
        }
      }
    }

    /// <summary>
    /// Command used to select a file.
    /// </summary>
    public ICommand SelectFileCommand
    {
      get 
      {
        return 
          _selectFileCommand ??
            (_selectFileCommand = 
              new DelegateCommand(
                SelectFileCommandExecuted, 
                CanExecuteSelectFileCommand));
      }
    }

    /// <summary>
    /// Command used to remove a file from selection.
    /// </summary>
    public ICommand UnselectFileCommand
    {
      get 
      {
        return 
          _unselectFileCommand ??
            (_unselectFileCommand = 
              new DelegateCommand(
                UnselectFileCommandExecuted, 
                CanExecuteUnselectFileCommand));
      }
    }

    /// <summary>
    /// Represents the command used to change the order of 
    /// <see cref="SelectedFile"/> to a higher position.
    /// </summary>
    public ICommand MoveUpCommand
    {
      get 
      {
        return 
          _moveUpCommand ?? 
            (_moveUpCommand = 
              new DelegateCommand(
                MoveUpCommandExecuted, 
                CanExecuteMoveUpCommand));
      }
    }

    /// <summary>
    /// Represents the command used to change the order of 
    /// <see cref="SelectedFile"/> to a lower position.
    /// </summary>
    public ICommand MoveDownCommand
    {
      get 
      {
        return 
          _moveDownCommand ??
            (_moveDownCommand = 
              new DelegateCommand(
                MoveDownCommandExecuted, 
                CanExecutedMoveDownCommand));
      }
    }

    /// <summary>
    /// Gets the selected files based on <see cref="FileNames"/>
    /// </summary>
    public List<IOViewModel> SelectedFiles
    {
      get
      {
        return FileNames.Values.ToList();
      }
    }

    /// <summary>
    /// Gets or sets the selected directory file which 
    /// might be moved to the overall selection.
    /// </summary>
    public IOViewModel SelectedDirectoryFile
    {
      get { return _selectedDirectoryFile; }
      set
      {
        if (value == _selectedDirectoryFile) return;
        _selectedDirectoryFile = value;
        HandlePropertyChanged();
        ((DelegateCommand)SelectFileCommand).RaiseCanExecuteChanged();
      }
    }

    /// <summary>
    /// Represents one of the files being member of the selection list.
    /// This selection can be used to enable the user to deselect it again.
    /// </summary>
    public IOViewModel SelectedFile
    {
      get { return _selectedFile; }
      set
      {
        if (value == _selectedFile) return;
        _selectedFile = value;
        HandlePropertyChanged();

        ((DelegateCommand)MoveUpCommand).RaiseCanExecuteChanged();
        ((DelegateCommand)MoveDownCommand).RaiseCanExecuteChanged();
        ((DelegateCommand)UnselectFileCommand).RaiseCanExecuteChanged();
      }
    }

    /// <summary>
    /// Gets the list of the files belonging to the current selected directory which are not already selected.
    /// </summary>
    public ObservableCollection<IOViewModel> UnselectedDirectoryFileNames
    {
      get
      {
        return _unselectedDirectoryFileNames;
      }
      set
      {
        if (value == _unselectedDirectoryFileNames) return;

        if (_unselectedDirectoryFileNames != null)
          foreach (IOViewModel unselectedDirectoryFileName in _unselectedDirectoryFileNames)
            unselectedDirectoryFileName.Dispose();

        _unselectedDirectoryFileNames = value;
        HandlePropertyChanged();
      }
    }

    /// <summary>
    /// Represents the view model used to select directories.
    /// </summary>
    public DirectorySelectionViewModel DirectorySelectionViewModel
    {
      get 
      {
        if (_directorySelectionViewModel == null)
        {
          _directorySelectionViewModel = new DirectorySelectionViewModel();
          _directorySelectionViewModel.PropertyChanged += DirectorySelectionViewModelOnPropertyChanged;
        }

        return _directorySelectionViewModel;
      }
    }

    /// <summary>
    /// Gets or sets the selected files.
    /// </summary>
    public SortedDictionary<int, IOViewModel> FileNames
    {
      get
      {
        return 
          _fileNames ?? 
            (_fileNames =
              new SortedDictionary<int, IOViewModel>());
      }
      set
      {
        _fileNames = value;
        HandlePropertyChanged();
      }
    }

    /// <summary>
    /// Command used when the dialog was canceled.
    /// </summary>
    public ICommand CancelDialogCommand { get; private set; }

    /// <summary>
    /// Command used when the dialog was accepted.
    /// </summary>
    public ICommand AcceptDialogCommand { get; private set; }

    /// <summary>
    /// Represents the service used to get directories.
    /// </summary>
    protected IDirectoryService DirectoryService
    {
      get
      {
        var filesSelectionDialogNotification =
          Notification as FilesSelectionDialogNotification;

        return 
          filesSelectionDialogNotification == null ? 
            null : 
            filesSelectionDialogNotification.DirectoryService;
      }
    }

    /// <summary>
    /// Represents the event aggregator
    /// </summary>
    protected IEventAggregator EventAggregator
    {
      get
      {
        var filesSelectionDialogNotification =
          Notification as FilesSelectionDialogNotification;

        return
          filesSelectionDialogNotification == null ? 
            null : 
            filesSelectionDialogNotification.EventAggregator;
      }
    }

    #endregion

    #region methods

    /// <summary>
    /// Occurs when a property of <see cref="ZoomViewModel"/> changes.
    /// </summary>
    /// <param name="sender">Not used.</param>
    /// <param name="e">Not used.</param>
    protected void ZoomViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      if (e.PropertyName == "ZoomFactor")
      {
        IEventAggregator eventAggregator = EventAggregator;
        if (eventAggregator != null)
        {
          eventAggregator.GetEvent<ApplicationZoomFactorChangedEvent>().Publish(ZoomViewModel.ZoomFactor);
        }
      }

      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("ZoomViewModel");
    }

    /// <summary>
    /// Gets whether <see cref="SelectFileCommand"/> can/may be executed.
    /// </summary>
    /// <returns>True if the command may be executed.</returns>
    protected bool CanExecuteSelectFileCommand()
    {
      return SelectFileCommandIsEnabled;
    }

    /// <summary>
    /// <see cref="SelectFileCommand"/> should be executed.
    /// </summary>
    protected void SelectFileCommandExecuted()
    {
      if (!SelectFileCommandIsEnabled) return;

      var newKeyValue = FileNames.Any() ? FileNames.Keys.Max() + 1 : 0;
      FileNames.Add(
        newKeyValue,
        SelectedDirectoryFile);

      UnselectedDirectoryFileNames.Remove(SelectedDirectoryFile);
      SelectedDirectoryFile = null;
      SelectedFile = FileNames[newKeyValue];
      
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("FileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("UnselectedDirectoryFileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFiles");
    }

    /// <summary>
    /// Gets whether <see cref="UnselectFileCommand"/> can/may be executed.
    /// </summary>
    /// <returns>True if <see cref="UnselectFileCommand"/></returns> can/may be executed.
    protected bool CanExecuteUnselectFileCommand()
    {
      return UnselectFileCommandIsEnabled;
    }

    /// <summary>
    /// <see cref="UnselectFileCommand"/> should be executed.
    /// </summary>
    protected void UnselectFileCommandExecuted()
    {
      if (!UnselectFileCommandIsEnabled) return;

      var keyOfSelectedFile = OrderOfSelectedFile;
      if (keyOfSelectedFile == -1) return;  // Shouldn´t happen.
      string pathOfSelectedFile = FileNames[keyOfSelectedFile].FullPath;

      #region handle FileNames

      FileNames.Remove(keyOfSelectedFile);
      
      SelectedFile = null;

      #endregion

      #region handle unselected files

      UpdateUnselectedDirectoryFileNames();
      var foundUnselectedFile = UnselectedDirectoryFileNames.FirstOrDefault(unselectedFile => unselectedFile.FullPath == pathOfSelectedFile);
      if (foundUnselectedFile != null) 
        SelectedDirectoryFile = foundUnselectedFile;

      #endregion

      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("FileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("UnselectedDirectoryFileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFiles");
    }

    /// <summary>
    /// Gets whether <see cref="MoveUpCommand"/> may be executed.
    /// </summary>
    /// <returns>True if <see cref="MoveUpCommand"/></returns> may be executed.
    protected bool CanExecuteMoveUpCommand()
    {
      return MoveUpCommandIsEnabled;
    }

    /// <summary>
    /// <see cref="MoveUpCommand"/> should be executed.
    /// </summary>
    protected void MoveUpCommandExecuted()
    {
      if (!MoveUpCommandIsEnabled) return;

      // ReSharper disable once PossibleInvalidOperationException
      var displayOrderOfSelectedItem = OrderOfSelectedFile;
      if (displayOrderOfSelectedItem == -1) return; // Shouldn´t happen.

      // Exchange both items:
      var temp = FileNames[displayOrderOfSelectedItem];
      FileNames[displayOrderOfSelectedItem] = FileNames[displayOrderOfSelectedItem - 1];
      FileNames[displayOrderOfSelectedItem - 1] = temp;

      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("FileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFiles");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFile");
      ((DelegateCommand)MoveUpCommand).RaiseCanExecuteChanged();
      ((DelegateCommand)MoveDownCommand).RaiseCanExecuteChanged();
    }

    /// <summary>
    /// Gets whether <see cref="MoveDownCommand"/> can/may be executed.
    /// </summary>
    /// <returns></returns>
    protected bool CanExecutedMoveDownCommand()
    {
      return MoveDownCommandIsEnabled;
    }

    /// <summary>
    /// <see cref="MoveDownCommand"/> should be executed.
    /// </summary>
    protected void MoveDownCommandExecuted()
    {
      if (!MoveDownCommandIsEnabled) return;

      // ReSharper disable once PossibleInvalidOperationException
      var displayOrderOfSelectedItem = OrderOfSelectedFile;
      if (displayOrderOfSelectedItem == -1) return; // Shouldn´t happen.

      // Exchange both items:
      var temp = FileNames[displayOrderOfSelectedItem];
      FileNames[displayOrderOfSelectedItem] = FileNames[displayOrderOfSelectedItem + 1];
      FileNames[displayOrderOfSelectedItem + 1] = temp;

      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("FileNames");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFiles");
      // ReSharper disable once ExplicitCallerInfoArgument
      HandlePropertyChanged("SelectedFile");
      ((DelegateCommand)MoveUpCommand).RaiseCanExecuteChanged();
      ((DelegateCommand)MoveDownCommand).RaiseCanExecuteChanged();
    }

    /// <summary>
    /// Listens to the event occuring when the selected directory 
    /// of <see cref="DirectorySelectionViewModel"/> changes.
    /// </summary>
    /// <param name="sender">Should be <see cref="DirectorySelectionViewModel"/></param>
    /// <param name="propertyChangedEventArgs">Used to get the name of the changed property.</param>
    protected void DirectorySelectionViewModelOnPropertyChanged(
      object sender, PropertyChangedEventArgs propertyChangedEventArgs)
    {
      if (!(sender is DirectorySelectionViewModel)) return;

      switch (propertyChangedEventArgs.PropertyName)
      {
        case "SelectedDirectory":
          UpdateUnselectedDirectoryFileNames();
          break;
      }
    }

    /// <summary>
    /// Updates <see cref="UnselectedDirectoryFileNames"/>
    /// </summary>
    protected void UpdateUnselectedDirectoryFileNames()
    {
      if (DirectoryService == null) return;
      if (DirectorySelectionViewModel == null) return;

      List<string> allFilesOfSelectedDirectory =
        DirectoryService != null && DirectorySelectionViewModel != null && DirectorySelectionViewModel.SelectedDirectory != null ? 
          DirectoryService.GetDirectoryFiles(
            DirectorySelectionViewModel.SelectedDirectory.FullPath) : 
          new List<string>();

      if (FileNames.Any() && allFilesOfSelectedDirectory != null)
      {
        foreach (KeyValuePair<int, IOViewModel> orderFileNamePair in FileNames)
        {
          if (allFilesOfSelectedDirectory.Contains(orderFileNamePair.Value.FullPath))
            allFilesOfSelectedDirectory.Remove(orderFileNamePair.Value.FullPath);
        }
      }

      List<IOViewModel> result = 
        allFilesOfSelectedDirectory == null ? 
          new List<IOViewModel>() : 
          allFilesOfSelectedDirectory.Select(
            currentFileName => 
              new IOViewModel(
                currentFileName, 
                DirectorySelectionViewModel.SelectedDirectory, 
                DirectoryService)).ToList();

      UnselectedDirectoryFileNames = new ObservableCollection<IOViewModel>(result);
    }

    /// <summary>
    /// Disposes this object.
    /// </summary>
    /// <param name="isDisposing">Represents whether this object is disposing.</param>
    protected override void Dispose(bool isDisposing)
    {
      if (_isDisposed) return;

      if (isDisposing)
      {
        #region dispose managed resources

        if (ZoomViewModel != null)
        {
          _zoomViewModel.PropertyChanged -= ZoomViewModelPropertyChanged;
          _zoomViewModel.Dispose();
          _zoomViewModel = null;
        }

        if (_directorySelectionViewModel != null)
        {
          _directorySelectionViewModel.Dispose();
          _directorySelectionViewModel.PropertyChanged -= DirectorySelectionViewModelOnPropertyChanged;
          _directorySelectionViewModel = null;
        }

        #endregion
      }

      #region clean up unmanaged resources

      #endregion

      _isDisposed = true;
    }

    /// <summary>
    /// <see cref="CancelDialogCommand"/> was executed.
    /// </summary>
    protected void CancelDialogCommandInteraction()
    {
      if (Notification != null)
      {
        if (_filesSelectionDialogNotification.FileNames != null)
          foreach (KeyValuePair<int, IOViewModel> orderAndFileKVP in _filesSelectionDialogNotification.FileNames)
            orderAndFileKVP.Value.Dispose();

        _filesSelectionDialogNotification.FileNames = null;
        _filesSelectionDialogNotification.Confirmed = false;
      }

      FinishInteraction();
    }

    /// <summary>
    /// <see cref="AcceptDialogCommand"/> was executed.
    /// </summary>
    protected void AcceptDialogCommandInteraction()
    {
      if (Notification != null)
      {
        _filesSelectionDialogNotification.FileNames = FileNames;
        _filesSelectionDialogNotification.Confirmed = true;
      }

      FinishInteraction();
    }

    #endregion

    #region IInteractionRequestAware

    /// <summary>
    /// An <see cref="T:System.Action"/> that can be invoked to finish the interaction.
    /// </summary>
    public Action FinishInteraction
    {
      get;
      set;
    }

    /// <summary>
    /// The <see cref="T:Microsoft.Practices.Prism.Interactivity.InteractionRequest.INotification"/> passed when the interaction request was raised.
    /// </summary>
    public INotification Notification
    {
      get { return _filesSelectionDialogNotification; }
      set
      {
        if (!(value is FilesSelectionDialogNotification)) return;
        _filesSelectionDialogNotification = value as FilesSelectionDialogNotification;
        DirectorySelectionViewModel.DirectoryService = DirectoryService;
        ZoomViewModel.ZoomFactor = _filesSelectionDialogNotification.InitialZoomFactor;
        HandlePropertyChanged();
      }
    }

    #endregion
  }
}
